package net.lindeer.lang

import java.lang.RuntimeException

class Interpreter : Expr.Visitor<Any?>,
        Stmt.Visitor<Unit> {
    private val globalEnv = Environment()
    private var environment = globalEnv
    private val envDepth = mutableMapOf<Expr, Int>()

    init {
        globalEnv.define("clock", object : RoxCallable {
            override val arity = 0

            override fun call(interpreter: Interpreter, arguments: List<Any?>): Any {
                return System.currentTimeMillis().toDouble() / 1000
            }

            override fun toString(): String {
                return "<native clock>"
            }
        })
    }

    override fun visitBinaryExpr(expr: Expr.Binary): Any? {
        val left = evaluate(expr.left)
        val right = evaluate(expr.right)
        return when (expr.operator.type) {
            Token.Type.GREATER -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double > right as Double
            }
            Token.Type.GREATER_EQUAL -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double >= right as Double
            }
            Token.Type.LESS -> {
                checkNumberOperand(expr.operator, left, right)
                (left as Double) < (right as Double)
            }
            Token.Type.LESS_EQUAL -> {
                checkNumberOperand(expr.operator, left, right)
                (left as Double) <= (right as Double)
            }
            Token.Type.NOT -> {
                !isEqual(left, right)
            }
            Token.Type.EQUAL_EQUAL -> {
                isEqual(left, right)
            }
            Token.Type.MINUS -> {
                checkNumberOperand(expr.operator, left, right)
                (left as Double) - (right as Double)
            }
            Token.Type.PLUS -> {
                if (left is Double && right is Double) {
                    left + right
                } else if (left is String && right is String) {
                    left + right
                } else {
                    throw RuntimeError(expr.operator, "Operands must be two numbers or strings!")
                }
            }
            Token.Type.SLASH -> {
                checkNumberOperand(expr.operator, left, right)
                (left as Double) / (right as Double)
            }
            Token.Type.STAR -> {
                checkNumberOperand(expr.operator, left, right)
                (left as Double) * (right as Double)
            }
            else -> null
        }
    }

    override fun visitGroupingExpr(expr: Expr.Grouping): Any? {
        return evaluate(expr.expression)
    }

    override fun visitLiteralExpr(expr: Expr.Literal): Any? {
        return expr.value
    }

    override fun visitUnaryExpr(expr: Expr.Unary): Any? {
        val right = evaluate(expr.right)
        return when (expr.operator.type) {
            Token.Type.MINUS -> {
                checkNumberOperand(expr.operator, right)
                -(right as Double)
            }
            Token.Type.NOT -> !isTruthy(right)
            else -> null
        }
    }

    override fun visitVariableExpr(expr: Expr.Variable): Any? {
        return lookupVariable(expr.name, expr)
    }

    override fun visitAssignExpr(expr: Expr.Assign): Any? {
        val value = evaluate(expr.value)
        envDepth[expr]?.let { depth ->
            environment.assignAt(depth, expr.name, value)
        } ?: globalEnv.assign(expr.name, value)
        return value
    }

    override fun visitLogicalExpr(expr: Expr.Logical): Any? {
        val left = evaluate(expr.left)
        val leftIsTrue = isTruthy(left)
        return if (expr.operator.type == Token.Type.OR) {
            if (leftIsTrue) {
                left
            } else {
                evaluate(expr.right)
            }
        } else {
            if (!leftIsTrue) {
                left
            } else {
                evaluate(expr.right)
            }
        }
    }

    override fun visitCallExpr(expr: Expr.Call): Any? {
        val callee = evaluate(expr.callee)
        val arguments = expr.arguments.map { evaluate(it) }.toList()
        val callable = callee as? RoxCallable ?: throw RuntimeError(expr.paren,
                "Can only call function and class!")

        val arity = callable.arity
        if (arguments.size != arity) {
            throw RuntimeError(expr.paren, "Expect $arity arguments but got ${arguments.size}!")
        }
        return callable.call(this, arguments)
    }

    // statement
    override fun visitExpressionStmt(stmt: Stmt.Expression) {
        val expression = stmt.expression
        evaluate(expression)
    }

    override fun visitPrintStmt(stmt: Stmt.Print) {
        val value = evaluate(stmt.expression)
        println(stringify(value))
    }

    override fun visitReturnStmt(stmt: Stmt.Return) {
        val v = stmt.value?.let { evaluate(it) }
        throw ReturnSignal(v)
    }

    override fun visitVarStmt(stmt: Stmt.Var) {
        val value = stmt.initializer?.let { evaluate(it) }
        environment.define(stmt.name.lexeme, value)
    }

    override fun visitWhileStmt(stmt: Stmt.While) {
        while (isTruthy(evaluate(stmt.condition))) {
            execute(stmt.body)
        }
    }

    override fun visitBlockStmt(stmt: Stmt.Block) {
        executeBlock(stmt.statements, Environment(environment))
    }

    override fun visitIfStmt(stmt: Stmt.If) {
        if (isTruthy(evaluate(stmt.condition))) {
            execute(stmt.thenStmt)
        } else if (stmt.elseStmt != null) {
            execute(stmt.elseStmt)
        }
    }

    override fun visitFuncStmt(stmt: Stmt.Func) {
        val func = RoxFunction(stmt, Environment(environment))
        environment.define(stmt.name.lexeme, func)
    }

    override fun visitClazzStmt(stmt: Stmt.Clazz) {
        val superclass = stmt.superclass?.let {
            evaluate(it) as? RoxClass ?: throw RuntimeError(it.name, "Super class must be a class!")
        }
        environment.define(stmt.name.lexeme, null)
        val subEnv = superclass?.let {
            val cur = environment
            environment = Environment(cur)
            environment.define("super", superclass)
            cur
        }
        val methods = stmt.methods.map {
            val key = it.name.lexeme
            key to RoxFunction(it, environment, key == "init")
        }.toMap()
        val clazz = RoxClass(stmt.name.lexeme, superclass, methods)
        subEnv?.let {
            environment = subEnv
        }
        environment.assign(stmt.name, clazz)
    }

    override fun visitGetterExpr(expr: Expr.Getter): Any? {
        return (evaluate(expr.who) as? RoxInstance)?.get(expr.name)
                ?: throw RuntimeError(expr.name, "Only instance has properties!")
    }

    override fun visitSetterExpr(expr: Expr.Setter): Any? {
        val who = evaluate(expr.who) as? RoxInstance
                ?: throw RuntimeError(expr.name, "Only instance have fields!")
        return evaluate(expr.right)?.also { who.set(expr.name, it) }
    }

    override fun visitThisExpr(expr: Expr.This): Any? {
        return lookupVariable(expr.keyword, expr)
    }

    override fun visitSuperExpr(expr: Expr.Super): Any? {
        val depth = envDepth[expr]!!
        val superclass = environment.getAt(depth, "super") as RoxClass
        val who = environment.getAt(depth - 1, "this") as RoxInstance
        val method = superclass.findMethod(expr.method.lexeme)
                ?: throw RuntimeError(expr.method," Undefine property '${expr.method.lexeme}'!")
        return method.bind(who)
    }

    private fun evaluate(expr: Expr): Any? {
        return expr.accept(this)
    }

    private fun isTruthy(obj: Any?): Boolean {
        return obj as? Boolean ?: (obj != null)
    }

    private fun isEqual(l: Any?, r: Any?): Boolean {
        return l == r
    }

    private fun checkNumberOperand(operator: Token, vararg operands: Any?) {
        operands.forEach { operand ->
            if (operand !is Double) throw RuntimeError(operator, "Operand must be a number!")
        }
    }

    private fun lookupVariable(name: Token, expr: Expr): Any? {
        return envDepth[expr]?.let { depth ->
            environment.getAt(depth, name.lexeme)
        } ?: globalEnv.get(name)
    }

    fun interpreter(statements: List<Stmt>) {
        try {
            statements.forEach {
                execute(it)
            }
        } catch (e: RuntimeError) {
            Rox.runtimeError(e)
        }
    }

    private fun execute(statement: Stmt) {
        statement.accept(this)
    }

    fun executeBlock(statements: List<Stmt>, env: Environment) {
        val prevEnv = environment
        try {
            environment = env
            statements.forEach { stmt ->
                execute(stmt)
            }
        } finally {
            environment = prevEnv
        }
    }

    fun resolve(expr: Expr, depth: Int) {
        envDepth[expr] = depth
    }

    private fun stringify(any: Any?): String {
        return (any as? Double)?.let {
            val text = "%.2f".format(it)
            if (text.endsWith("0")) text.substring(0, text.length - 2)
            else text
        } ?: (any?.toString() ?: "null")

    }
}

class RuntimeError(val token: Token, message: String) : RuntimeException(message)

class ReturnSignal(val value: Any?) : RuntimeException(null, null, false, false)
